// 进程池的编写
#include "Task.hpp"
#include <assert.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define PROCESSNUM 5  // 定义宏，表示多少子进程和父进程进行管道-进程通信 

int waitCommand(int fd)  // 等待命令
{
    unsigned int number = 0;
    ssize_t n = read(fd, &number, sizeof(number));
    if (n == 0)  // 读到文件末尾了，表示对面的写端已经关闭了
    {
        close(fd);  // 可以把读端关闭了
        return -1;
    }
    assert(n == sizeof(unsigned int));
    return number;
}

void sendAndWakeup(int fd, int command)  // 派发任务
{
    // std::cout << "父进程派发任务下标-" << command << std::endl; 
    ssize_t n = write(fd, &command, sizeof(command));
    assert(n >= 0);
    (void)n;
    std::cout << "父进程派发了一次任务" << "->" << desc[command] << std::endl;
}

int main()
{
    // 首先装载任务
    load();
    // 存储每个进程对应的打开的管道文件fd
    std::vector<std::pair<pid_t, int>> v;
    for (size_t i = 0; i < PROCESSNUM; i++)
    {
        // 首先创建匿名管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;  // 防止n被优化掉

        pid_t id = fork();
        assert(id >= 0);
        if (id == 0)
        {
            // 子进程 - 管道的读端、关闭写端
            close(pipefd[1]);
            // 循环的去读取
            while(true)
            {
                int nums = waitCommand(pipefd[0]);  // 传读端文件fd即可 - 并且上面没有写入会阻塞在这里，等待派发任务
                if (nums < 0) break;

                // std::cout << getpid() << "进程接收任务下标-" << nums << std::endl; 
                if (nums < funcSize()) callFunc[nums]();  // 执行对应任务函数
                else std::cout << "非法执行任务下标" << std::endl;
                std::cout << std::endl;
            }
            exit(1);  // 子进程执行到这里直接退出即可
        }
        // 父进程区域 - 写端，关闭读端
        close(pipefd[0]);
        // 记录一下父进程写端，方便后序进行关闭fd的指向
        v.push_back(std::make_pair(id, pipefd[1]));
    }
    
    // 父进程派发任务
    for (int i = 0; i < 20; i++)  // 假设循环十次即可
    {
        // 循环进行
        srand((unsigned int)time(nullptr));  // 种子
        int command = rand() % funcSize();  // 随机选择个任务下标进行派发
        int spro = rand() % v.size();  // 随机选择一个子进程进行派发任务

        sendAndWakeup(v[spro].second, command);
        sleep(1);
    }

    // 父进程关闭写端-子进程对应写端关闭后也会关闭读端，管道自动回收
    for (const auto& e : v)
    {
        close(e.second);
    }

    // 回收子进程，防止变成僵尸进程
    for (const auto& e : v)
    {
        waitpid(e.first, nullptr, 0);  // 阻塞式等待
    }
    return 0;
}